#region File Description

//-----------------------------------------------------------------------------
// MediaHandler.cs
//
// Author: ITU Team3 (2012)
//-----------------------------------------------------------------------------

#endregion File Description

#region Using Statements

using System;
using System.Collections.Generic;
using System.Linq;
using rentit03_server.Database;
using rentit03_server.OtherServices;
using Version = rentit03_server.Database.Version;

#endregion Using Statements

namespace rentit03_server.Business
{
    public static class MediaHandler
    {
        private static readonly DataAccessObject Dao = new DataAccessObject();

        /// <summary>
        /// Create media in database
        /// </summary>
        /// <param name="media">Media</param>
        /// <param name="sessionId">Session id</param>
        /// <exception cref="MediaHandlerException">Throws exception if media is invalid</exception>
        public static int CreateMedia(RentitMedia media, string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.CreateMedia, sessionId, true);

            IsValidMedia(media);
            if (media.Type.ToLower().Equals("movie"))
            {
                var m = (RentitMovie)media;

                m = MovieFetcher.FetchDataFromService(m, MovieFetcher.ApiService.DeanClatworthy);
                m = MovieFetcher.FetchDataFromService(m, MovieFetcher.ApiService.ImdbApi);

                var movie = m.ToMovie();

                return Dao.CreateMedia(movie);
            }
            throw new MediaHandlerException(ReturnStatus.MediaValidateType, "Could not convert mediatype");
        }

        /// <summary>
        /// Check if a medium is valid
        /// </summary>
        private static bool IsValidMedia(RentitMedia media)
        {
            if (String.IsNullOrWhiteSpace(media.Title))
                throw new MediaHandlerException(ReturnStatus.MediaValidateTitle, "Media title must be set");

            if (media.Year <= 0)
                throw new MediaHandlerException(ReturnStatus.MediaValidateYear, "Media year must be positive");

            if (Dao.MediaExists(media.ToMedia()))
            {
                throw new MediaHandlerException(ReturnStatus.MediaDuplicateMovieTitleYear,
                                                "A media with same title and year already exists");
            }

            return true;
        }

        /// <summary>
        /// Get media from database
        /// </summary>
        /// <param name="mediaId">Media id</param>
        /// <param name="sessionId">Session id</param>
        /// <returns>Media</returns>
        public static RentitMedia ReadMedia(int mediaId, string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.ReadMedia, sessionId, true);

            var m = Dao.ReadMedia(mediaId);

            if (m == null)
                throw new MediaHandlerException(ReturnStatus.MediaInvalidMediaId, "The media id didn't match any movie");

            var type = m.GetType().ToString();

            if (m.GetType() == typeof(Movie))
            {
                var m2 = (Movie)m;
                return m2.ToRentitMovie(sessionId);
            }

            throw new MediaHandlerException(ReturnStatus.MediaValidateType, "Could not convert mediatype (" + type + ") @ Mediahandler.ReadMedia");
        }

        /// <summary>
        /// Get media from database by providing a version id
        /// </summary>
        /// <param name="versionId">Version id</param>
        /// <param name="sessionId">Session id</param>
        /// <returns>RentitMedia</returns>
        public static RentitMedia ReadMediaFromVersionId(int versionId, string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.ReadMedia, sessionId, true);

            var m = Dao.ReadMediaFromVersionId(versionId);

            if (m == null)
                throw new MediaHandlerException(ReturnStatus.MediaInvalidMediaId, "The media id didn't match any movie");

            if (m.GetType() == typeof(Movie))
            {
                var m2 = (Movie)m;
                return m2.ToRentitMovie(sessionId);
            }

            throw new MediaHandlerException(ReturnStatus.MediaValidateType, "Could not convert mediatype");
        }

        /// <summary>
        /// Update a media in the database. The existing media in the database will be replaced with the new media.
        /// Comments, Reviews and versions are not considered to be part of the movie and is not edited with this method.
        /// The id can not be edited.
        /// </summary>
        /// <param name="rentitMedia">New media</param>
        /// <param name="mediaId">Media id</param>
        /// <param name="sessionId">Session id</param>
        public static void EditMedia(RentitMedia rentitMedia, int mediaId, string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.EditMedia, sessionId, true);

            if (!rentitMedia.Type.ToLower().Equals("movie"))
                throw new RentitException(ReturnStatus.MediaValidateType, "Type not supported");

            rentitMedia = MovieFetcher.FetchDataFromService((RentitMovie)rentitMedia, MovieFetcher.ApiService.DeanClatworthy);
            rentitMedia = MovieFetcher.FetchDataFromService((RentitMovie)rentitMedia, MovieFetcher.ApiService.ImdbApi);

            //load media from db
            var m = Dao.ReadMedia(mediaId);
            var media = (Movie)m;

            //update values
            if (!String.IsNullOrWhiteSpace(rentitMedia.Title))
                media.Title = rentitMedia.Title;

            if (rentitMedia.Year > 0)
                media.Year = rentitMedia.Year;

            if (!String.IsNullOrWhiteSpace(rentitMedia.CoverUrl))
                media.CoverUrl = rentitMedia.CoverUrl;

            if (!String.IsNullOrWhiteSpace(rentitMedia.Description))
                media.Description = rentitMedia.Description;

            if (rentitMedia.Votes > 0)
                media.Votes = rentitMedia.Votes;

            if (rentitMedia.Rating > 0)
                media.Rating = rentitMedia.Rating;

            if (rentitMedia.Released != null)
                media.Released = DateTime.Parse(rentitMedia.Released);

            media.Updated = DateTime.Now;

            if (rentitMedia.Genres != null)
            {
                media.Genres.Clear();
                foreach (var g in rentitMedia.Genres)
                    media.Genres.Add(new Genre { MediaType = rentitMedia.Type, Name = g.Name });
            }

            if (rentitMedia.Images != null)
            {
                media.Images.Clear();
                foreach (var i in rentitMedia.Images)
                    media.Images.Add(new Image { MediaId = media.Id, Url = i });
            }

            if (rentitMedia.Languages != null)
            {
                media.Languages.Clear();
                foreach (var l in rentitMedia.Languages)
                    media.Languages.Add(new Language { Name = l });
            }

            // if movie
            if (rentitMedia.Type.ToLower().Equals("movie"))
            {
                var rentitMovie = (RentitMovie)rentitMedia;
                if (rentitMovie.ImdbId != null)
                    media.ImdbId = rentitMovie.ImdbId;

                if (rentitMovie.ImdbUrl != null)
                    media.ImdbUrl = rentitMovie.ImdbUrl;

                if (rentitMovie.Rated != null)
                    media.Rated = rentitMovie.Rated;

                if (rentitMovie.Runtime > 0)
                    media.Runtime = rentitMovie.Runtime;
            }

            // write back to db
            Dao.EditMedia(media, mediaId);
        }

        /// <summary>
        /// Delete media in the database
        /// </summary>
        /// <param name="mediaId">Media id</param>
        /// <param name="sessionId">Session id</param>
        public static void DeleteMedia(int mediaId, string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.DeleteMedia, sessionId, true);
            Dao.DeleteMedia(mediaId);
        }

        /// <summary>
        /// Get a list of movies in the database
        /// </summary>
        /// <param name="sessionId">Session id</param>
        /// <returns>A list of all movies in the database</returns>
        /// <exception cref="SessionHandlerException">Throws exception if session id is invalid</exception>
        public static List<RentitMedia> OverviewMedia(string sessionId = "")
        {
            return OverviewMedia(new MediaSearchParameter(), sessionId);
        }

        /// <summary>
        /// Get a list of medias in the database fulfilling the conditions
        /// </summary>
        /// <param name="condition">The conditions for the medias on the list</param>
        /// <param name="sessionId">Session id</param>
        /// <returns>A list of all medias in the database fulfilling the conditions</returns>
        /// <exception cref="SessionHandlerException">Throws exception if session id is invalid</exception>
        public static List<RentitMedia> OverviewMedia(MediaSearchParameter condition, string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.OverviewMedia, sessionId, true);

            var ms = Dao.OverviewMedia(condition);

            var media = new List<RentitMedia>();
            foreach (var m in ms)
            {
                if (m.GetType() == typeof(Movie))
                {
                    var m2 = (Movie)m;
                    media.Add(m2.ToRentitMovie(sessionId));
                }
                else
                {
                    throw new MediaHandlerException(ReturnStatus.MediaValidateType, "Could not convert mediatype");
                }
            }
            return media;
        }

        /// <summary>
        /// Get a list of all version related to a medium
        /// </summary>
        /// <param name="mediaId">Id of the medium</param>
        /// <param name="sessionId">Session id</param>
        /// <returns>List of versions</returns>
        public static List<RentitVersion> OverviewVersions(int mediaId, string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.OverviewMediaVersions, sessionId, true);
            var versions = Dao.OverviewVersions(mediaId);

            return versions.Select(v => v.ToRentitVersion()).ToList();
        }

        /// <summary>
        /// Convert a RentitMedia object to a format understtod by the data layer
        /// </summary>
        /// <param name="rentitMedia">RentitMedia object</param>
        /// <returns>Media object</returns>
        public static Media ToMedia(this RentitMedia rentitMedia)
        {
            return new Media
                       {
                           Title = rentitMedia.Title,
                           Year = rentitMedia.Year
                       };
        }

        /// <summary>
        /// Convert a RentitVersion object to a format understood by the data layer
        /// </summary>
        /// <param name="rentitVersion">A RentitVersion object</param>
        /// <returns>A Version Object</returns>
        public static Version ToVersion(this RentitVersion rentitVersion)
        {
            var version = new Version
                              {
                                  MediaId = rentitVersion.MediaId,
                                  RentDays = rentitVersion.RentDays,
                                  Price = rentitVersion.Price,
                              };
            if (!String.IsNullOrWhiteSpace(rentitVersion.Format))
                version.Format = rentitVersion.Format;
            if (!String.IsNullOrWhiteSpace(rentitVersion.Resolution))
                version.Resolution = rentitVersion.Resolution;
            return version;
        }

        /// <summary>
        /// Convert a RentitMovie object to a format understood by the data layer.
        /// </summary>
        /// <param name="rentitMovie"></param>
        /// <returns></returns>
        public static Movie ToMovie(this RentitMovie rentitMovie)
        {
            var movie = new Movie
            {
                Id = rentitMovie.Id,
                Title = rentitMovie.Title,
                Year = rentitMovie.Year,
                CoverUrl = rentitMovie.CoverUrl,
                Description = rentitMovie.Description,
                Rating = rentitMovie.Rating,
                Votes = rentitMovie.Votes,
                Rated = rentitMovie.Rated,
                Runtime = rentitMovie.Runtime,
                ImdbId = rentitMovie.ImdbId,
                ImdbUrl = rentitMovie.ImdbUrl,
            };
            DateTime rel;
            if (DateTime.TryParse(rentitMovie.Released, out rel))
                movie.Released = rel;

            if (rentitMovie.Versions != null)
            {
                foreach (var v in new HashSet<RentitVersion>(rentitMovie.Versions))
                {
                    movie.Versions.Add(new Version
                    {
                        MediaId = v.MediaId,
                        Resolution = v.Resolution,
                        Format = v.Format,
                        RentDays = v.RentDays,
                        Price = v.Price
                    });
                }
            }

            if (rentitMovie.Comments != null)
            {
                foreach (var c in rentitMovie.Comments)
                {
                    movie.Comments.Add(new Comment { Text = c.Comment, UserId = c.UserId, Created = DateTime.Now });
                }
            }

            if (rentitMovie.Genres != null)
            {
                foreach (var g in new HashSet<RentitGenre>(rentitMovie.Genres))
                {
                    movie.Genres.Add(new Genre { MediaType = rentitMovie.Type, Name = g.Name });
                }
            }

            if (rentitMovie.Images != null)
            {
                foreach (var i in new HashSet<string>(rentitMovie.Images))
                    movie.Images.Add(new Image { MediaId = rentitMovie.Id, Url = i });
            }

            if (rentitMovie.Languages != null)
            {
                foreach (var l in new HashSet<string>(rentitMovie.Languages))
                    movie.Languages.Add(new Language { Name = l });
            }

            return movie;
        }

        /// <summary>
        /// Convert a Movie object to a RentitMovie.
        /// User permissions determines the contents of the object.
        /// </summary>
        /// <param name="movie">A Movie object</param>
        /// <param name="sessionId">Session id</param>
        /// <returns>A RentitMovie object</returns>
        public static RentitMovie ToRentitMovie(this Movie movie, string sessionId)
        {
            var rentitMovie = new RentitMovie
            {
                Id = movie.Id,
                Title = movie.Title,
                Year = movie.Year,
                CoverUrl = movie.CoverUrl,
                Description = movie.Description,
                Type = "Movie",
                Rating = (float)movie.Rating,
                Updated = movie.Updated.ToString()
            };

            if (movie.Votes != null) rentitMovie.Votes = (int)movie.Votes;
            if (movie.Released != null) rentitMovie.Released = movie.Released.ToString();

            if (movie.Runtime != null) rentitMovie.Runtime = (int)movie.Runtime;
            if (movie.ImdbId != null) rentitMovie.ImdbId = movie.ImdbId;
            if (movie.ImdbUrl != null) rentitMovie.ImdbUrl = movie.ImdbUrl;
            if (movie.Rated != null) rentitMovie.Rated = movie.Rated;

            foreach (var v in movie.Versions)
            {
                rentitMovie.Versions.Add(new RentitVersion
                {
                    Id = v.Id,
                    MediaId = v.MediaId,
                    Resolution = v.Resolution,
                    Format = v.Format,
                    RentDays = v.RentDays,
                    Price = v.Price,
                });
            }

            foreach (var g in movie.Genres)
                rentitMovie.Genres.Add(new RentitGenre
                    {
                        MediaType = g.MediaType,
                        Name = g.Name,
                    });

            foreach (var i in movie.Images)
                rentitMovie.Images.Add(i.Url);

            foreach (var l in movie.Languages)
                rentitMovie.Languages.Add(l.Name);

            foreach (var c in movie.Comments)
                rentitMovie.Comments.Add(c.ToRentitComment());

            return rentitMovie;
        }

        /// <summary>
        /// Converts a Version object to a RentitVersion.
        /// </summary>
        /// <param name="version">A Version object</param>
        /// <returns>A RentitVersion object</returns>
        public static RentitVersion ToRentitVersion(this Version version)
        {
            return new RentitVersion
            {
                Id = version.Id,
                MediaId = version.MediaId,
                Resolution = version.Resolution,
                Format = version.Format,
                RentDays = version.RentDays,
                Price = version.Price,
                Movie = null, //(version.Medium as Movie).ToRentitMovie(String.Empty)
            };
        }

        /// <summary>
        /// Get a list of all genres in the database
        /// </summary>
        /// <param name="sessionId">Session id</param>
        /// <returns>List of genres as strings</returns>
        public static List<string> OverviewGenres(string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.OverviewMedia, sessionId, true);

            var gs = Dao.OverviewGenre();
            return gs.Select(g => g.Name).ToList();
        }

        /// <summary>
        /// Get a list of all languages in the database
        /// </summary>
        /// <param name="sessionId">Session id</param>
        /// <returns>List of languages as strings</returns>
        public static List<string> OverviewLanguages(string sessionId)
        {
            SessionHandler.UserAccess(PermissionEnum.OverviewMedia, sessionId, true);

            var ls = Dao.OverviewLanguage();
            return ls.Select(l => l.Name).ToList();
        }

        public static List<RentitMedia> TopRatedMovies(int page, int count, string sessionId)
        {
            return Dao.TopRatedMovies(page, count).Select(m => m.ToRentitMovie(sessionId)).ToList<RentitMedia>();
        }

        public static List<RentitMedia> RandomMovies(int count, string sessionId)
        {
            return Dao.RandomMovies(count).Select(m => m.ToRentitMovie(sessionId)).ToList<RentitMedia>();
        }

        public static int OverviewMediaCount(MediaSearchParameter parem, string sessionId)
        {
            return Dao.OverviewMediaCount(parem);
        }
    }

    /// <summary>
    /// Representation of a media
    /// </summary>
    public class RentitMedia
    {
        public RentitMedia()
        {
            Versions = new List<RentitVersion>();
            Genres = new List<RentitGenre>();
            Images = new List<string>();
            Languages = new List<string>();
            Comments = new List<RentitComment>();
        }

        public int Id { get; set; }

        public string Type { get; set; }

        public string Title { get; set; }

        public int Year { get; set; }

        public string CoverUrl { get; set; }

        public string Description { get; set; }

        public int Votes { get; set; }

        public float Rating { get; set; }

        public string Updated { get; set; }

        public string Released { get; set; }

        public List<RentitVersion> Versions { get; set; }

        public List<RentitGenre> Genres { get; set; }

        public List<string> Images { get; set; }

        public List<string> Languages { get; set; }

        public List<RentitComment> Comments { get; set; }
    }

    /// <summary>
    /// Representation of a Movie
    /// </summary>
    public class RentitMovie : RentitMedia
    {
        public string ImdbId { get; set; }

        public string ImdbUrl { get; set; }

        public string Rated { get; set; }

        public int Runtime { get; set; }
    }

    /// <summary>
    /// Representation of a Genre
    /// </summary>
    public class RentitGenre
    {
        public string Name { get; set; }

        public string MediaType { get; set; }

        public override bool Equals(Object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof(RentitGenre)) return false;
            return Equals((RentitGenre)obj);
        }

        public bool Equals(RentitGenre other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.Name.ToLower(), Name.ToLower()) && Equals(other.MediaType.ToLower(), MediaType.ToLower());
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ (MediaType != null ? MediaType.GetHashCode() : 0);
            }
        }
    }

    /// <summary>
    /// Representation of a Version
    /// </summary>
    public class RentitVersion
    {
        public int Id { get; set; }

        public int MediaId { get; set; }

        public string Resolution { get; set; }

        public string Format { get; set; }

        public int? RentDays { get; set; }

        public int Price { get; set; }

        public RentitMedia Movie { get; set; }
    }

    /// <summary>
    /// Custom exception used by the MediaHandler
    /// </summary>
    public class MediaHandlerException : RentitException
    {
        public MediaHandlerException(ReturnStatus status, string message) : base(status, message) { }
    }
}